﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace Campus
{
    public class Minimap
    {
        /// <summary>
        /// The Player that this minimap is drawing information for.
        /// </summary>
        public Player Owner
        {
            get;
            private set;
        }

        /// <summary>
        /// The position of the upper-left corner of the minimap.
        /// </summary>
        public Vector2 StartPosition
        {
            get;
            set;
        }

        /// <summary>
        /// The size in pixels of this minimap.
        /// </summary>
        public Vector2 Size
        {
            get;
            private set;
        }

        /// <summary>
        /// The texture being used to draw dots for the minimap.
        /// </summary>
        public Texture2D MinimapDotTexture
        {
            get;
            private set;
        }

        /// <summary>
        /// This Dictionary is an index that maps the Minimap terrain Sprites to their respective Level terrain Sprites.
        /// </summary>
        public Dictionary<Sprite, Sprite> LevelSpriteIndex
        {
            get;
            private set;
        }

        /// <summary>
        /// The portion of the minimap that draws the terrain.  Since terrain is static, this only needs to be drawn once and then units can be drawn over it.
        /// 
        /// This does not currently get updated when this Minimap object's Update() function is called in order to increase speed.
        /// </summary>
        public Sprite[,] TerrainMinimap
        {
            get;
            private set;
        }

        public List<Sprite> TerrainMinimapCollection
        {
            get;
            private set;
        }

        /// <summary>
        /// The portion of the minimap that draws the units.
        /// 
        /// This gets updated when this Minimap object's Update() function is called.
        /// </summary>
        public List<Sprite> UnitMinimap
        {
            get;
            private set;
        }

        /// <summary>
        /// The portion of the minimap that draws the box representing the player's vision.
        /// 
        /// This gets updated when this Minimap object's Update() function is called.
        /// </summary>
        public List<Sprite> VisionBoxMinimap
        {
            get;
            private set;
        }

        private Vector2 m_pixelsPerPoint = Vector2.Zero;
        private Vector2 m_levelSizeInPixels = Vector2.Zero;

        /// <summary>
        /// Create a static Minimap object which can be updated and drawn.
        /// </summary>
        /// <param name="owner">The Player that this minimap is drawing information for.</param>
        /// <param name="startPosition">The position of the upper-left corner of the minimap.</param>
        /// <param name="size">The size in pixels of this minimap.</param>
        /// <param name="content">The ContentManager to load the MinimapDotTexture with.</param>
        public Minimap(Player owner, Vector2 startPosition, Vector2 size, Vector2 levelSizeInPixels, ContentManager content)
        {
            Owner = owner;
            StartPosition = startPosition;
            Size = size;
            m_levelSizeInPixels = levelSizeInPixels;
            MinimapDotTexture = content.Load<Texture2D>("HUD/minimapPoint");

            LevelSpriteIndex = new Dictionary<Sprite, Sprite>();

            UnitMinimap = new List<Sprite>();
            TerrainMinimapCollection = new List<Sprite>();
            VisionBoxMinimap = new List<Sprite>();

            //BuildTerrainMinimap();
            BuildTerrainMinimapUsingSprites();
        }

        public void Update()
        {
            UnitMinimap.Clear();
            VisionBoxMinimap.Clear();

            // Update the UnitMinimap with visible items.
            List<Sprite> visibleSprites = Owner.VisibleSprites;
            foreach (Sprite sprite in visibleSprites)
            {
                if (sprite is Unit)
                {
                    Unit unit = (Unit)sprite;

                    Color color = unit.PlayerOwner.TeamColor;

                    Vector2 position = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / unit.Position.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / unit.Position.Y)));
                    Vector2 drawSize = new Vector2(m_pixelsPerPoint.X, m_pixelsPerPoint.Y);
                    Vector2 origin = Vector2.Zero;

                    UnitMinimap.Add(new Sprite(MinimapDotTexture, position, drawSize, origin, color, SpriteEffects.None, 1.0f, MinimapCollisionAction));
                }

                if (sprite is Structure)
                {
                    Structure structure = (Structure)sprite;

                    Color color = structure.PlayerOwner.TeamColor;

                    Vector2 position = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / structure.Position.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / structure.Position.Y)));
                    Vector2 drawSize = new Vector2(m_pixelsPerPoint.X * structure.TilesOccupied.Count / 3, m_pixelsPerPoint.Y * structure.TilesOccupied.Count / 3);
                    // Adjust in the Y for buildings.
                    position.Y -= drawSize.Y / 2;
                    Vector2 origin = Vector2.Zero;

                    UnitMinimap.Add(new Sprite(MinimapDotTexture, position, drawSize, origin, color, SpriteEffects.None, 1.0f, MinimapCollisionAction));
                }
            }

            // Update the VisionBoxMinimap with the player's vision box by converting screen position coordinates to minimap coordinates.
            Vector2 upperLeft = new Vector2(Owner.ScreenPosition.X, Owner.ScreenPosition.Y + 51);   // Account for the 51 pixels of top HUD.
            Vector2 upperLeftInMinimap = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / upperLeft.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / upperLeft.Y)));

            Vector2 upperRight = new Vector2(upperLeft.X + ResolutionHandler.WindowWidth, upperLeft.Y);
            Vector2 upperRightInMinimap = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / upperRight.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / upperRight.Y)));

            Vector2 bottomLeft = new Vector2(upperLeft.X, upperLeft.Y + ResolutionHandler.WindowHeight - 160);    // Account for the 160 pixels of bottom HUD.
            Vector2 bottomLeftInMinimap = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / bottomLeft.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / bottomLeft.Y)));

            Vector2 bottomRight = new Vector2(upperLeft.X + ResolutionHandler.WindowWidth, upperLeft.Y + ResolutionHandler.WindowHeight - 160);   // Account for the 160 pixels of bottom HUD.
            Vector2 bottomRightInMinimap = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / bottomRight.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / bottomRight.Y)));
            
            // Draw the top, hack in 1 pixel of extra width to avoid rounding issues.
            VisionBoxMinimap.Add(new Sprite(MinimapDotTexture, upperLeftInMinimap, new Vector2(upperRightInMinimap.X - upperLeftInMinimap.X + 1, 1.0f), Vector2.Zero, Color.White, SpriteEffects.None, 1.0f, MinimapCollisionAction));
            // Draw the left.
            VisionBoxMinimap.Add(new Sprite(MinimapDotTexture, upperLeftInMinimap, new Vector2(1.0f, bottomLeftInMinimap.Y - upperLeftInMinimap.Y), Vector2.Zero, Color.White, SpriteEffects.None, 1.0f, MinimapCollisionAction));
            // Draw the right.
            VisionBoxMinimap.Add(new Sprite(MinimapDotTexture, upperRightInMinimap, new Vector2(1.0f, bottomRightInMinimap.Y - upperRightInMinimap.Y), Vector2.Zero, Color.White, SpriteEffects.None, 1.0f, MinimapCollisionAction));
            // Draw the bottom, hack in 1 pixel of extra width to avoid rounding issues.
            VisionBoxMinimap.Add(new Sprite(MinimapDotTexture, bottomLeftInMinimap, new Vector2(bottomRightInMinimap.X - bottomLeftInMinimap.X + 1, 1.0f), Vector2.Zero, Color.White, SpriteEffects.None, 1.0f, MinimapCollisionAction));
            
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Begin();

            // Draw the terrain background based on Level's sprites.
            foreach (Sprite sprite in TerrainMinimapCollection)
            {
                Color tileColor = sprite.Color;

                // If the Sprite isn't visible, darken the color by half.
                if (!Owner.VisibleSprites.Contains(LevelSpriteIndex[sprite]))
                {
                    Vector3 tileColorVector3 = tileColor.ToVector3();
                    tileColor = new Color(tileColorVector3.X / 2, tileColorVector3.Y / 2, tileColorVector3.Z / 2);
                }

                Rectangle destinationRect = new Rectangle((int)sprite.Position.X, (int)sprite.Position.Y, (int)sprite.Size.X, (int)sprite.Size.Y);

                spriteBatch.Draw(sprite.Texture, destinationRect, null, tileColor, sprite.Rotation, sprite.Origin, SpriteEffects.None, sprite.LayerDepth);
            }

            // Draw the units onto the terrain.
            foreach (Sprite sprite in UnitMinimap)
            {
                Rectangle destinationRect = new Rectangle((int)sprite.Position.X, (int)sprite.Position.Y, (int)sprite.Size.X, (int)sprite.Size.Y);

                spriteBatch.Draw(sprite.Texture, destinationRect, null, sprite.Color, sprite.Rotation, sprite.Origin, SpriteEffects.None, sprite.LayerDepth);
            }

            // Draw your "box of vision."
            foreach (Sprite sprite in VisionBoxMinimap)
            {
                Rectangle destinationRect = new Rectangle((int)sprite.Position.X, (int)sprite.Position.Y, (int)sprite.Size.X, (int)sprite.Size.Y);

                spriteBatch.Draw(sprite.Texture, destinationRect, null, sprite.Color, sprite.Rotation, sprite.Origin, SpriteEffects.None, sprite.LayerDepth);
            }

            spriteBatch.End();
        }

        /// <summary>
        /// Returns a 2D array of sprites which can be drawn to the screen with their set positions to draw a minimap.
        /// </summary>
        /// <param name="startPosition">The screen position of the upper-left corner of the minimap.</param>
        /// <param name="size">The width (x) and height (y) of the minimap to be drawn.</param>
        /// <param name="pointTexture">The texture to be used when creating a point on the minimap.</param>
        /// <returns></returns>
        private void BuildTerrainMinimap()
        {
            World playerWorld = Owner.CurrentLevel.WorldObject;

            int drawWidth = (int)Size.X;
            int drawHeight = (int)Size.Y;

            // Figure out how many pixels to use per tile.
            m_pixelsPerPoint.X = drawWidth / playerWorld.Width / 2;
            m_pixelsPerPoint.Y = drawHeight / playerWorld.Height;
            int tilesPerPointX = 1;
            int tilesPerPointY = 1;

            // If there's less than 1 pixel per point, then we need to take tiles per point instead.
            if (m_pixelsPerPoint.X < 1)
            {
                m_pixelsPerPoint.X = 1;
                tilesPerPointX = (int)Math.Floor((double)(playerWorld.Width / drawWidth));
            }
            if (m_pixelsPerPoint.Y < 1)
            {
                m_pixelsPerPoint.Y = 1;
                tilesPerPointY = (int)Math.Floor((double)(playerWorld.Height / drawHeight));
            }

            // Go get the terrain grid 
            List<Terrain>[,] terrainGrid = playerWorld.TerrainGrid;
            int mapHeight = terrainGrid.GetLength(0);
            int mapWidth = terrainGrid.GetLength(1);

            // Build the minimap with a 2D array of sprites.
            float currentX, currentY;
            Sprite[,] minimapPoints = new Sprite[mapHeight, mapWidth];
            for (int y = 0; y < mapHeight; y += tilesPerPointY)
            {
                currentX = m_pixelsPerPoint.X * y;
                currentY = (m_pixelsPerPoint.Y / 2) * (mapWidth - 1 + y);

                for (int x = 0; x < mapWidth; x += tilesPerPointX)
                {
                    List<Terrain> tileTerrainList = terrainGrid[y, x];
                    // Color using the top-most terrain piece.
                    Terrain terrainToColorWith = tileTerrainList[tileTerrainList.Count - 1];
                    Color color = Owner.CurrentLevel.Tiles[terrainToColorWith].MinimapColor;

                    Vector2 position = new Vector2(StartPosition.X + (int)currentX, StartPosition.Y + (int)currentY);
                    Vector2 drawSize = new Vector2(m_pixelsPerPoint.X, m_pixelsPerPoint.Y);
                    Vector2 origin = Vector2.Zero;

                    minimapPoints[y, x] = new Sprite(MinimapDotTexture, position, drawSize, origin, color, SpriteEffects.None, .99f, MinimapCollisionAction);

                    currentX += m_pixelsPerPoint.X;
                    currentY -= m_pixelsPerPoint.Y / 2;
                }
            }

            TerrainMinimap = minimapPoints;
        }

        public void BuildTerrainMinimapUsingSprites()
        {
            // Remove the current list of terrain sprites since this needs to be called 
            // when changing the minimap's location and you don't want a new copy of the
            // minimap each time you move it
            TerrainMinimapCollection.Clear();

            World playerWorld = Owner.CurrentLevel.WorldObject;

            int drawWidth = (int)Size.X;
            int drawHeight = (int)Size.Y;

            // Figure out how many pixels to use per tile.
            m_pixelsPerPoint.X = drawWidth / playerWorld.Width / 2;
            m_pixelsPerPoint.Y = drawHeight / playerWorld.Height;

            // Go get the Level's list of terrain sprites.
            List<Sprite> levelTerrainCollection = Owner.CurrentLevel.BackgroundTerrainCollection;
            levelTerrainCollection.AddRange(Owner.CurrentLevel.ForegroundTerrainCollection);
            Dictionary<Sprite, Color> levelMinimapColors = Owner.CurrentLevel.MinimapColors;

            foreach (Sprite tile in levelTerrainCollection)
            {
                Color color = Color.Violet;
                if (levelMinimapColors.ContainsKey(tile))
                    color = levelMinimapColors[tile];

                Vector2 position = new Vector2(StartPosition.X + (Size.X / (m_levelSizeInPixels.X / tile.Position.X)), StartPosition.Y + (Size.Y / (m_levelSizeInPixels.Y / tile.Position.Y)));
                // Draw size is hacked to add one to both dimensions because there were some issues with 1-pixel gaps between tiles.
                Vector2 drawSize = new Vector2(m_pixelsPerPoint.X + 1, m_pixelsPerPoint.Y + 1);
                Vector2 origin = Vector2.Zero;

                Sprite newTerrainMinimapSprite = new Sprite(MinimapDotTexture, position, drawSize, origin, color, SpriteEffects.None, 1.0f, MinimapCollisionAction);

                TerrainMinimapCollection.Add(newTerrainMinimapSprite);
                LevelSpriteIndex.Add(newTerrainMinimapSprite, tile);
            }
        }

        private void MinimapCollisionAction(Sprite sprite1, Sprite sprite2)
        {
        }
    }
}
